package gew.table;

import gew.beans.DataFacade2;
import gew.sql.SqlTemplates;
import gew.utils.SqlUtils;
import java.util.ArrayList;
import java.util.List;

public class Table {

    private final String factTableName;
    private final String locale;
    private final ArrayList<TableDimension> verDimensions;
    private final ArrayList<TableDimension> horDimensions;
    private final ArrayList<TableDimension> horDimensionsReverse;
    private final ArrayList<TableDimension> verDimensionsReverse;
    private final ArrayList<TableMeasure> measures;
    private int measCount;
    private ArrayList<TableRow> rows;
    private final ArrayList<TableColumn> columns;
    private int dataColsCount;
    private final int bottomHeaderLimit;
    private final int rightHeaderLimit;
    private boolean dataLoaded;

    public Table(String factTableName, String locale, int bottomHeaderLimit, int rightHeaderLimit) {
        this.factTableName = factTableName;
        this.locale = locale;
        verDimensions = new ArrayList<>();
        horDimensions = new ArrayList<>();
        horDimensionsReverse = new ArrayList<>();
        verDimensionsReverse = new ArrayList<>();
        measures = new ArrayList<>();
        measCount = 0;
        rows = new ArrayList<>();
        columns = new ArrayList<>();
        this.bottomHeaderLimit = bottomHeaderLimit;
        this.rightHeaderLimit = rightHeaderLimit;
        dataLoaded = false;
    }

    public void addVerticalDimension(int hierGroupId, String factTableField, String title, String ids, boolean showInTable, boolean showInBalloon, String css, boolean showCode, boolean noParents) {
        this.verDimensions.add(new TableDimension(verDimensions.size(), hierGroupId, factTableField, title, ids, showInTable, showInBalloon, css, showCode, noParents));
        reverseDimensions(verDimensions, verDimensionsReverse);
    }

    public void addVerticalDimension(int hierGroupId, String factTableField, String title, String ids, boolean showInTable, boolean showInBalloon, String css, boolean showCode) {
        this.verDimensions.add(new TableDimension(verDimensions.size(), hierGroupId, factTableField, title, ids, showInTable, showInBalloon, css, showCode));
        reverseDimensions(verDimensions, verDimensionsReverse);
    }

    public void addHorizontalDimension(int hierGroupId, String factTableField, String title, String ids, boolean showInTable, boolean showInBalloon, String css, boolean showCode, boolean noParents) {
        this.horDimensions.add(new TableDimension(horDimensions.size(), hierGroupId, factTableField, title, ids, showInTable, showInBalloon, css, showCode, noParents));
        reverseDimensions(horDimensions, horDimensionsReverse);
    }

    public void addHorizontalDimension(int hierGroupId, String factTableField, String title, String ids, boolean showInTable, boolean showInBalloon, String css, boolean showCode) {
        this.horDimensions.add(new TableDimension(horDimensions.size(), hierGroupId, factTableField, title, ids, showInTable, showInBalloon, css, showCode));
        reverseDimensions(horDimensions, horDimensionsReverse);
    }

    public void addMeasure(String factTableField, String title, boolean showInTable, boolean showInBalloon, TableMeasure.OrderBy orderBy) {
        this.measures.add(new TableMeasure(measures.size(), factTableField, title, showInTable, showInBalloon, orderBy));
        for (int i = 0; i < this.measures.size(); i++) {
            if (this.measures.get(i).isShowInTable()) {
                measCount++;
            }
        }
        if (measCount == 1) {
            measCount = 0;
        }
    }

    private static void reverseDimensions(ArrayList<TableDimension> source, ArrayList<TableDimension> target) {
        target.clear();
        for (int i = source.size() - 1; i >= 0; i--) {
            target.add(source.get(i));
        }
    }

    public void loadData(DataFacade2 df) {

        // <editor-fold defaultstate="collapsed" desc="Fill columns">
        for (int m = 0; m < this.measures.size(); m++) {
            ArrayList<TableColumn> cs = new ArrayList<>();
            cs.add(new TableColumn(0, this.measures.get(m)));
            for (int d = 0; d < verDimensions.size(); d++) {
                cs = fillColumns(d, cs);
            }
            this.columns.addAll(cs);
        }

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Compose Load SQL">
        String q1 = SqlUtils.SQL_SELECT
                .concat(SqlUtils.SPACE)
                .concat(SqlUtils.SQL_ALIAS_D)
                .concat(SqlUtils.POINT)
                .concat(SqlUtils.SQL_ALIAS_ID);
        String sqlMeasureTemplate = SqlTemplates.getSql(SqlTemplates.TABLE_COLUMN);
        for (int i = 0; i < columns.size(); i++) {
            q1 = q1
                    .concat(SqlUtils.NEW_LINE)
                    .concat(sqlMeasureTemplate
                            .replace("@locale@", locale)
                            .replace("@ifCondition@", columns.get(i).getSqlIfCondition())
                            .replace("@factTableField@", columns.get(i).getMeasure().getFactTableField()));
        }
        q1 = q1
                .concat(SqlUtils.NEW_LINE)
                .concat(SqlUtils.SQL_FROM)
                .concat(SqlUtils.BRACKET_LEFT);
        for (int r = 0; r < rows.size(); r++) {
            if (r > 0) {
                q1 = q1
                        .concat(SqlUtils.SPACE)
                        .concat(SqlUtils.SQL_UNION_ALL)
                        .concat(SqlUtils.NEW_LINE);
            }
            q1 = q1.concat(rows.get(r).getSql());
        }
        q1 = q1
                .concat(SqlUtils.BRACKET_RIGHT)
                .concat(SqlUtils.SQL_ALIAS_D)
                .concat(SqlUtils.NEW_LINE)
                .concat(SqlUtils.SQL_LEFT_JOIN)
                .concat(SqlUtils.SPACE)
                .concat(this.factTableName)
                .concat(SqlUtils.SPACE)
                .concat(SqlUtils.SQL_ALIAS_F)
                .concat(SqlUtils.SPACE)
                .concat(SqlUtils.SQL_ON)
                .concat(SqlUtils.SPACE)
                .concat(SqlUtils.SQL_ALWAYS_TRUE);
        for (int i = 0; i < horDimensions.size(); i++) {
            q1 = q1
                    .concat(SqlUtils.NEW_LINE)
                    .concat(SqlUtils.SQL_AND)
                    .concat(SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_ALIAS_D)
                    .concat(SqlUtils.POINT)
                    .concat(horDimensions.get(i).getFactTableField())
                    .concat(SqlUtils.EQUALS)
                    .concat(SqlUtils.SQL_ALIAS_F)
                    .concat(SqlUtils.POINT)
                    .concat(horDimensions.get(i).getFactTableField());
        }
        for (int i = 0; i < horDimensions.size(); i++) {
            q1 = q1
                    .concat(SqlUtils.NEW_LINE)
                    .concat(SqlUtils.SQL_AND)
                    .concat(SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_ALIAS_F)
                    .concat(SqlUtils.POINT)
                    .concat(horDimensions.get(i).getFactTableField())
                    .concat(SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_IN)
                    .concat(SqlUtils.BRACKET_LEFT)
                    .concat(horDimensions.get(i).getLadedValuesIdsCS())
                    .concat(SqlUtils.BRACKET_RIGHT);
        }
        for (int i = 0; i < verDimensions.size(); i++) {
            q1 = q1
                    .concat(SqlUtils.NEW_LINE)
                    .concat(SqlUtils.SQL_AND)
                    .concat(SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_ALIAS_F)
                    .concat(SqlUtils.POINT)
                    .concat(verDimensions.get(i).getFactTableField())
                    .concat(SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_IN)
                    .concat(SqlUtils.BRACKET_LEFT)
                    .concat(verDimensions.get(i).getLadedValuesIdsCS())
                    .concat(SqlUtils.BRACKET_RIGHT);
        }
        q1 = q1
                .concat(SqlUtils.NEW_LINE)
                .concat(SqlUtils.SQL_GROUP_BY);
        for (int i = 0; i < horDimensions.size(); i++) {
            q1 = q1
                    .concat(i > 0 ? SqlUtils.COMMA : SqlUtils.SPACE)
                    .concat(SqlUtils.SQL_ALIAS_D)
                    .concat(SqlUtils.POINT)
                    .concat(horDimensions.get(i).getFactTableField());
        }
        q1 = q1
                .concat(SqlUtils.NEW_LINE)
                .concat(SqlUtils.SQL_ORDER_BY)
                .concat(SqlUtils.SPACE)
                .concat(SqlUtils.QUOTE)
                .concat(SqlUtils.QUOTE);

        String sqlMeasureValueTemplate = SqlTemplates.getSql(SqlTemplates.TABLE_COLUMN_VALUE);
        String sqlOrdM = SqlUtils.EMPTY;
        for (int i = 0; i < columns.size(); i++) {
            if (columns.get(i).getMeasure().getOrderBy() != TableMeasure.OrderBy.NONE) {
                sqlOrdM = sqlOrdM
                        .concat(sqlMeasureValueTemplate
                                .replace("@ifCondition@", columns.get(i).getSqlIfCondition())
                                .replace("@factTableField@", columns.get(i).getMeasure().getFactTableField()));
                if (columns.get(i).getMeasure().getOrderBy() == TableMeasure.OrderBy.DESC) {
                    sqlOrdM = sqlOrdM
                            .concat(SqlUtils.SPACE)
                            .concat(SqlUtils.SQL_DESC);
                }
            }
        }
        for (int i = 0; i < horDimensions.size() - (sqlOrdM.isEmpty() ? 0 : 1); i++) {
            q1 = q1
                    .concat(SqlUtils.COMMA)
                    .concat(SqlUtils.SQL_ALIAS_D)
                    .concat(SqlUtils.POINT)
                    .concat(SqlUtils.SQL_ALIAS_OB)
                    .concat(horDimensions.get(i).getIndex().toString());
        }
        q1 = q1.concat(sqlOrdM);

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Load columns values">
        rows = df.getTableMeasureValues(q1, this);

        setRowHDimCellsVisibility();

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="----------------">
        // </editor-fold>
        dataLoaded = true;
    }

    private ArrayList<TableColumn> fillColumns(int c, ArrayList<TableColumn> cs) {
        int colsOldCount = Math.max(1, cs.size());
        int blockSize = Math.max(1, verDimensions.get(c).getValues().size());
        int colsNewCount = colsOldCount * blockSize;
        for (int i = colsNewCount; i > colsOldCount; i--) {
            cs.add(null);
        }
        for (int i = colsNewCount - 1; i >= 0; i--) {
            int ii = i / blockSize;
            cs.set(i, new TableColumn(i, cs.get(ii)));
            cs.get(i).addDimensionValue(verDimensions.get(c).getValues().get(i - ii * blockSize));
        }
        return cs;
    }

    public TableRow getRowById(String id) {
        for (int r = 0; r < rows.size(); r++) {
            if (rows.get(r).getId().equals(id)) {
                return rows.get(r);
            }
        }
        return null;
    }

    public void loadDimensions(DataFacade2 df) {

        // <editor-fold defaultstate="collapsed" desc="Load H&V dimensions values">
        ArrayList<String> q1 = new ArrayList<>();
        for (int i = 0; i < verDimensions.size(); i++) {
            q1.add(verDimensions.get(i).getLoadSql(locale));
        }
        for (int i = 0; i < horDimensions.size(); i++) {
            q1.add(horDimensions.get(i).getLoadSql(locale));
        }
        ArrayList<ArrayList<TableDimensionValue>> values = df.getTableDimensionValues(q1);

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Fill table dimensions with loaded values">
        int c = 0;
        int iterateCount = 0;
        for (int i = 0; i < measures.size(); i++) {
            if (measures.get(i).isShowInTable()) {
                iterateCount++;
            }
        }
        for (int i = c; i < verDimensions.size(); i++) {
            verDimensions.get(i).setValues(values.get(i));
            verDimensions.get(i).setIterateCount(iterateCount);
            iterateCount = verDimensions.get(i).getValues().size() * iterateCount;
            c++;
        }
        iterateCount = 1;
        for (int i = 0; i < horDimensions.size(); i++) {
            horDimensions.get(i).setValues(values.get(i + c));
            horDimensions.get(i).setIterateCount(iterateCount);
            iterateCount = horDimensions.get(i).getValues().size() * iterateCount;
        }

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Set colspan & rowspan">
        int colSpan = 1;
        for (int i = verDimensions.size() - 1; i >= 0; i--) {
            verDimensions.get(i).setColspan(colSpan);
            colSpan = verDimensions.get(i).getValues().size() * colSpan;
        }
        dataColsCount = 0;
        for (int i = 0; i < measures.size(); i++) {
            measures.get(i).setColspan(colSpan);
            if (measures.get(i).isShowInTable()) {
                dataColsCount = dataColsCount + colSpan;
            }
        }

        int rowspan = 1;
        for (int i = horDimensions.size() - 1; i >= 0; i--) {
            horDimensions.get(i).setRowspan(rowspan);
            rowspan = horDimensions.get(i).getValues().size() * rowspan;
        }

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Fill rows">
        // </editor-fold>
        rows.add(new TableRow(0));
        boolean noRows = false;
        for (TableDimension hd : horDimensions) {
            if (hd.getValues().isEmpty()) {
                noRows = true;
                break;
            }
        }
        if (!noRows) {
            for (int d = 0; d < horDimensions.size(); d++) {
                fillRows(d);
            }
        }
        setRowHDimCellsVisibility();
    }

    private void setRowHDimCellsVisibility() {
        ArrayList<Integer> prevs = new ArrayList<>();
        for (int i = 0; i < this.horDimensions.size(); i++) {
            prevs.add(-1);
        }
        for (int r = 0; r < rows.size(); r++) {
            TableRow row = rows.get(r);
            boolean previousBreak = false;
            for (int d = 0; d < row.getHDimCells().size(); d++) {
                TableCellDimension cell = row.getHDimCells().get(d);
                int cellId = cell.getDim().getId();
                if (previousBreak || cellId != prevs.get(d)) {
                    cell.setVisible(true);
                    //cell.getDim().setRowspan(1);
                    prevs.set(d, cellId);
                    previousBreak = true;
                } else {
                    cell.setVisible(false);
                    //cell.getDim().setRowspan(cell.getDim().getRowspan() + 1);
                }
            }
        }
    }

    private void fillRows(int c) {
        int rowsOldCount = Math.max(1, rows.size());
        int blockSize = Math.max(1, horDimensions.get(c).getValues().size());
        int rowsNewCount = rowsOldCount * blockSize;
        for (int i = rowsNewCount; i > rowsOldCount; i--) {
            rows.add(null);
        }
        for (int i = rowsNewCount - 1; i >= 0; i--) {
            int ii = i / blockSize;
            rows.set(i, new TableRow(rows.get(ii), i));
            rows.get(i).addDimensionValue(horDimensions.get(c).getValues().get(i - ii * blockSize)/*, ii * blockSize == i*/);
        }
    }

    public Iterable<TableDimension> getVDim() {
        return verDimensions;
    }

    public Iterable<TableDimension> getVDimReverse() {
        return verDimensionsReverse;
    }

    public Iterable<TableDimension> getHDim() {
        return horDimensions;
    }

    public Iterable<TableDimension> getHDimReverse() {
        return horDimensionsReverse;
    }

    public Iterable<TableMeasure> getMeas() {
        return measures;
    }

    public int getVDimCount() {
        return verDimensions.size();
    }

    public int getMeasCount() {
        return measCount;
    }

    public Iterable<TableRow> getRows() {
        return rows;
    }

    public int getDataRowsCount() {
        return rows.size();
    }

    public int getDataColsCount() {
        return dataColsCount;
    }

    public List<TableColumn> getColumns() {
        return columns;
    }

    public boolean isShowRightHeader() {
        return this.columns.size() > rightHeaderLimit;
    }

    public boolean isShowBottomHeader() {
        return this.rows.size() > bottomHeaderLimit;
    }

    public boolean isDataLoaded() {
        return dataLoaded;
    }

}
